#include "StdAfx.h"
#include "ChunkDebugMesh.h"
#include "chunk.h"

#include "D3DRenderer.h"

#include "chunkmanager.h"
#include "chunkcamera.h"

#include "SpecifiedChunk.h"
#include "chunkboundary.h"

using namespace Common;
using namespace ChunkSystem;

// A structure for our custom vertex type
struct CUSTOMVERTEX
{
	Vector3 vPos;      // The untransformed, 3D position for the vertex
    DWORD	color;        // The vertex color
};

// Our custom FVF, which describes our custom vertex structure
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)

ChunkDebugMesh::ChunkDebugMesh(Chunk* pChunk)
	:ChunkItem::LoadProgressListener(pChunk),m_bBuiltFlag(false),m_pChunk(pChunk),
	m_pAABBVB(NULL),m_pAABBIB(NULL),m_pHullVB(NULL),m_pHullIB(NULL),m_iHullLineCount(0)
{
	assert(	m_pChunk!= NULL );

	m_uProgressInterval = 10;

	InitializeCriticalSection(&m_CriticalSection);

	m_pChunk->AttachLoadProgressListener(this);
}


ChunkDebugMesh::~ChunkDebugMesh(void)
{
	this->Unload();

	m_pChunk->DettachLoadProgressListener(this);

	DeleteCriticalSection(&m_CriticalSection);
}

void ChunkDebugMesh::Load( IRenderer* pRenderer )
{
	D3DRenderer* pDebugRenderer = dynamic_cast< D3DRenderer* >( pRenderer );
	if( pDebugRenderer == NULL )
		return;
	
	LPDIRECT3DDEVICE9 pDevice = pDebugRenderer->GetD3DDevice();

	EnterCriticalSection(&m_CriticalSection);

	if( !this->_BuildMeshAABBWire( pDevice ) )
		assert( false );
	
	//if( !this->_BuildMeshHullWire( pDevice ) )
		//assert( false );

	LeaveCriticalSection(&m_CriticalSection);
}

void ChunkDebugMesh::Unload( void )
{
	EnterCriticalSection(&m_CriticalSection);

	if( m_pAABBVB )
	{
		m_pAABBVB->Release();
		m_pAABBVB = NULL;
	}
	if( m_pAABBIB )
	{
		m_pAABBIB->Release();
		m_pAABBIB = NULL;
	}

	if( m_pHullVB )
	{
		m_pHullVB->Release();
		m_pHullVB = NULL;
	}
	if( m_pHullIB )
	{
		m_pHullIB->Release();
		m_pHullIB = NULL;
	}

	LeaveCriticalSection(&m_CriticalSection);
}

void ChunkDebugMesh::Draw( IRenderer* pRenderer )
{
	D3DRenderer* pDebugRenderer = dynamic_cast< D3DRenderer* >( pRenderer );
	if( pDebugRenderer == NULL )
		return;

	LPDIRECT3DDEVICE9 pDevice = pDebugRenderer->GetD3DDevice();

	EnterCriticalSection(&m_CriticalSection);
	
	this->_DrawMeshAABBWire(pDevice);
	this->_DrawMeshHullWire(pDevice);

	LeaveCriticalSection(&m_CriticalSection);
}

bool ChunkDebugMesh::_BuildMeshHullWire( LPDIRECT3DDEVICE9 pDevice )
{
	if( m_pChunk == NULL || pDevice == NULL)
		return false;

	if( m_pHullVB != NULL )
		return true;

	//only show the wire frame of internal chunk hull
	ExternalChunk* pExternalChunk = dynamic_cast< ExternalChunk* >( m_pChunk );
	if( pExternalChunk != NULL )
		return true;
	//create vb
	HullTree::HullOutLines rHullOutLines = m_pChunk->GetHullOutLines();
	HullTree::itrHullOutLines itrHullBorder = rHullOutLines.begin();

	m_iHullLineCount = 0;
	for( ; itrHullBorder != rHullOutLines.end(); ++itrHullBorder )
		m_iHullLineCount += (*itrHullBorder).points().size();

	if( FAILED( pDevice->CreateVertexBuffer( sizeof(CUSTOMVERTEX) * m_iHullLineCount, 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &m_pHullVB, NULL) ) )
		return false;

	CUSTOMVERTEX* pVBData;
	if( FAILED( m_pHullVB->Lock( 0, m_iHullLineCount * sizeof( CUSTOMVERTEX ), (void**)&pVBData, 0 ) ) )
		return false;
	{
		int iAccVBSize = 0;
		for( int iHullBorderIdx = 0 ; iHullBorderIdx != rHullOutLines.size(); ++iHullBorderIdx )
		{
			HullTree::Portal3D& rHullBorder = rHullOutLines[iHullBorderIdx];
			int iPointsCount = rHullBorder.points().size();
			CUSTOMVERTEX* pCurBorderVBBuffer = &pVBData[iAccVBSize];
			for( int iPts = 0; iPts != iPointsCount; ++iPts )
			{
				pCurBorderVBBuffer[iPts].vPos = rHullBorder.points()[iPts];
				pCurBorderVBBuffer[iPts].color = 0xffff0000;
			}
		}
	}
    m_pHullVB->Unlock();

	const Chunk::Boundaries& rInternalChunkBoundaries = m_pChunk->GetBoundaries();
	Chunk::ItrConstBounaries itrBoundary = rInternalChunkBoundaries.begin();
	for( ; itrBoundary != rInternalChunkBoundaries.end(); ++itrBoundary )
	{
		const Plane& rBoundaryPlane = (*itrBoundary)->GetPlane();
		ChunkBoundary::Portals& rPortals = (*itrBoundary)->GetUnboundPortals();

		for( int iPortalIdx = 0; iPortalIdx != rPortals.size(); ++iPortalIdx )
		{
			PortalMesh portalMesh;

			Portal* pPortal = rPortals[iPortalIdx];
			Vector3 uAxis = pPortal->GetAxisU();
			Vector3 vNormal = rBoundaryPlane.normal;
			Vector3 vAxis = vNormal.crossProduct( uAxis );

			Common::Matrix4 matBoundarySpace( Common::Matrix4::IDENTITY );
			matBoundarySpace[0][0] = uAxis[0], matBoundarySpace[1][0] = uAxis[1], matBoundarySpace[2][0] = uAxis[2];
			matBoundarySpace[0][1] = vAxis[0], matBoundarySpace[1][1] = vAxis[1], matBoundarySpace[2][1] = vAxis[2];
			matBoundarySpace[0][2] = vNormal[0], matBoundarySpace[1][2] = vNormal[1], matBoundarySpace[2][2] = vNormal[2];
			matBoundarySpace.setTrans( rBoundaryPlane.normal * rBoundaryPlane.d );

			Portal::PortalOutline& rPortalOutline = pPortal->GetOutLine();
			portalMesh.m_VertexCount = rPortalOutline.size();
			//create vb
			int iVBSize = rPortalOutline.size() + 1;
			if( FAILED( pDevice->CreateVertexBuffer( sizeof(CUSTOMVERTEX) * iVBSize, 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &portalMesh.m_pPortalVB, NULL ) ) )
				continue;

			CUSTOMVERTEX* pPortalVBData;
			if( FAILED( portalMesh.m_pPortalVB->Lock( 0, sizeof(CUSTOMVERTEX) * iVBSize , (void**)&pPortalVBData, 0 ) ) )
				continue;
			{
				for( int iOutlineIdx = 0; iOutlineIdx != iVBSize; ++iOutlineIdx )
				{
					int iPtsIdx = ( iOutlineIdx == rPortalOutline.size() ? 0 : iOutlineIdx );

					Vector3 pts( rPortalOutline[iPtsIdx].x, rPortalOutline[iPtsIdx].y, 0.0f );
					CUSTOMVERTEX& pPortalVtx = pPortalVBData[iOutlineIdx];
					pPortalVtx.vPos =  matBoundarySpace * pts;
					pPortalVtx.color = 0xff000000;
				}
			}
			portalMesh.m_pPortalVB->Unlock();

			m_conPortalMeshes.push_back( portalMesh );
		}
	}
	return true;
}

bool ChunkDebugMesh::_BuildMeshAABBWire( LPDIRECT3DDEVICE9 pDevice )
{
	if( m_pChunk == NULL || pDevice == NULL)
		return false;

	if( m_pAABBVB != NULL )
		return true;

	AxisAlignedBox aabb = m_pChunk->GetAABB();
	if( pDevice == NULL )
		return false;

	if( FAILED( pDevice->CreateVertexBuffer( sizeof(CUSTOMVERTEX) * 8, 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &m_pAABBVB, NULL) ) )
		return false;

	CUSTOMVERTEX* pVBData;
    if( FAILED( m_pAABBVB->Lock( 0,8 * sizeof( CUSTOMVERTEX ), (void**)&pVBData, 0 ) ) )
        return false;
	{
		for( int idx = AxisAlignedBox::FAR_LEFT_BOTTOM ; idx <= AxisAlignedBox::NEAR_RIGHT_BOTTOM ; ++idx )
		{
			pVBData[idx].vPos = aabb.getCorner( (AxisAlignedBox::CornerEnum) idx );
			pVBData[idx].color = D3DXCOLOR(1.0f,0.0f,0.0f,1.0f);
		}
	}
    m_pAABBVB->Unlock();

	if( FAILED( pDevice->CreateIndexBuffer( 12 * 2 * sizeof(UINT16) , 0 , D3DFMT_INDEX16 , D3DPOOL_MANAGED , &m_pAABBIB , NULL ) ) )
		return false;

	UINT16 indexAABB[] =
	{
		0,1 , 1,2 , 2,3 , 3,0 ,
		4,5 , 5,6 , 6,7 , 7,4 ,
		1,5 , 2,4 , 3,7 , 0,6
	};

	void* idxAABB;
	if( FAILED( m_pAABBIB->Lock( 0, sizeof( indexAABB ), &idxAABB, 0 ) ) )
		return false;

	{
		memcpy( idxAABB , indexAABB , sizeof(indexAABB) );
	}
    m_pAABBIB->Unlock();

	m_bBuiltFlag = true;

	return true;
}

void ChunkDebugMesh::_DrawMeshAABBWire( LPDIRECT3DDEVICE9 pDevice )
{
	if( pDevice == NULL )
		return;

	if( m_pAABBIB == NULL || m_pAABBVB == NULL )
		return;

	//mark current camera chunk for test
	ChunkCamera* pWorkingCamera = ChunkManager::GetInstance().GetChunkCamera(NULL);
	if( pWorkingCamera != NULL )
	{
		Chunk* pCurChunk = pWorkingCamera->GetCurChunk();

		AxisAlignedBox aabb = m_pChunk->GetAABB();
		CUSTOMVERTEX* pVBData;
		if( FAILED( m_pAABBVB->Lock( 0,8 * sizeof( CUSTOMVERTEX ), (void**)&pVBData, 0 ) ) )
			return;
		{
			if( pCurChunk == this->m_pChunk )
			{

				for( int idx = AxisAlignedBox::FAR_LEFT_BOTTOM ; idx <= AxisAlignedBox::NEAR_RIGHT_BOTTOM ; ++idx )
				{
					pVBData[idx].vPos = aabb.getCorner( (AxisAlignedBox::CornerEnum) idx );
					pVBData[idx].color = D3DXCOLOR(0.0f,1.0f,0.0f,1.0f);
				}
			}
			else
			{
				for( int idx = AxisAlignedBox::FAR_LEFT_BOTTOM ; idx <= AxisAlignedBox::NEAR_RIGHT_BOTTOM ; ++idx )
				{
					pVBData[idx].vPos = aabb.getCorner( (AxisAlignedBox::CornerEnum) idx );
					pVBData[idx].color = D3DXCOLOR(1.0f,0.0f,0.0f,1.0f);
				}
			}
		}
		m_pAABBVB->Unlock();
	}

	if( FAILED( pDevice->SetFVF(D3DFVF_CUSTOMVERTEX) ) )
		assert( false );

	if( FAILED( pDevice->SetStreamSource(0, m_pAABBVB, 0, sizeof(CUSTOMVERTEX) ) ) ) 
		assert( false );

	if( FAILED( pDevice->SetIndices(m_pAABBIB) ) )
		assert( false );

	if( FAILED( pDevice->DrawIndexedPrimitive( D3DPT_LINELIST, 0 , 0 , 8 , 0 , 12 ) ) )
		assert( false );


}

void ChunkDebugMesh::_DrawMeshHullWire( LPDIRECT3DDEVICE9 pDevice )
{
	if( pDevice == NULL )
		return;

	if( m_pHullVB == NULL )
		this->_BuildMeshHullWire( pDevice );

	if( FAILED( pDevice->SetFVF(D3DFVF_CUSTOMVERTEX) ) )
		assert( false );

	if( FAILED( pDevice->SetStreamSource(0, m_pHullVB, 0, sizeof(CUSTOMVERTEX) ) ) ) 
		assert( false );

	if( FAILED( pDevice->DrawPrimitive( D3DPT_LINELIST, 0, m_iHullLineCount ) ) )
		assert( false );

	//draw portals
	itrPortalMesh pos = m_conPortalMeshes.begin();
	for( ; pos != m_conPortalMeshes.end(); ++pos )
	{
		if( FAILED( pDevice->SetStreamSource(0, (*pos).m_pPortalVB, 0, sizeof(CUSTOMVERTEX) ) ) ) 
			assert( false );
		if( FAILED( pDevice->DrawPrimitive( D3DPT_LINESTRIP, 0, (*pos).m_VertexCount ) ) )
			assert( false );
	}
}

void ChunkDebugMesh::OnProgressIntervalNotify( uint16 uPercentage )
{
	if( m_pHullVB == NULL )
		return;

	EnterCriticalSection(&m_CriticalSection);

	if( this->m_pChunk->GetCurState() == Chunk::eUnload )
	{
		LeaveCriticalSection(&m_CriticalSection);
		return;
	}

	CUSTOMVERTEX* pVBData;
    if( FAILED( m_pHullVB->Lock( 0,8 * sizeof( CUSTOMVERTEX ), (void**)&pVBData, 0 ) ) )
        assert(false);
	{
		for( int idx = AxisAlignedBox::FAR_LEFT_BOTTOM ; idx <= AxisAlignedBox::NEAR_RIGHT_BOTTOM ; ++idx )
		{
			float fColorFactor = (float)uPercentage * 0.01f;
			pVBData[idx].color = D3DXCOLOR( fColorFactor, 1.0f - fColorFactor ,0.0f,1.0f);
		}
	}

	if( FAILED( m_pHullVB->Unlock() ) )
        assert(false);

	LeaveCriticalSection(&m_CriticalSection);
}
